﻿/*
 * This file is part of MonoStrategy.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://monostrategy.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


#if EMBEDDED
    using OpenTK.Graphics.ES20;
#else
using OpenTK.Graphics.OpenGL;
#endif

namespace MonoStrategy.RenderSystem
{
    public enum RenderPass
    {
        Pass1_Shadow,
        Pass2_Texture,
        Pass3_PlayerColor,
    }

    /// <summary>
    /// This class provides the finest level of control about visual rendering.
    /// It will render the given native texture according to all other given
    /// parameters.
    /// </summary>
    /// <remarks>
    /// At the beginning it might be confusing what exactly a width or height value,
    /// for example, means. The thing is that it depends on configuration. See
    /// <see cref="GLRenderer.SetupProjection"/> for more information. 
    /// Just thread virtual pixels as real pixel size, only scaled by internal parameters.
    /// 
    /// With OpenGL always keep in mind that the we have to call ALL GL commands
    /// withtin the same thread, usually the internal render loop thread. So any
    /// methods and properties exposed to the public MUSTN'T call any GL commands,
    /// instead the calls shall be deferred.
    /// </remarks>
    public abstract class RenderableVisual : PositionTracker
    {
        private static CrossRandom m_RenderShiftRandom = new CrossRandom(0);
        private byte m_Alpha = 255;

        /// <summary>
        /// The ratio between width and height initially passed to the constructor.
        /// </summary>
        public double AspectRatio { get; set; }
        /// <summary>
        /// Internally allows to set a custom texture at any time, may also be null.
        /// </summary>
        internal NativeTexture Texture { get; set; }
        /// <summary>
        /// The parent renderer.
        /// </summary>
        public TerrainRenderer Renderer { get; private set; }
        /// <summary>
        /// Default is one, and denotes the scaling factor for the whole rendered object.
        /// </summary>
        public Double Scale { get; internal set; }
        /// <summary>
        /// Should be rendered at all?
        /// </summary>
        public bool IsVisible { get; set; }
        /// <summary>
        /// Usuall set to 1.0, which also provides the fastest rendering using only one quad.
        /// For building animations, this will use a special subdivided, incomplete quad to
        /// so that by incrementally adding parts, the building gets more and more visible.
        /// </summary>
        public Double BuildingProgress { get; set; }

        public sbyte PositionShiftX { get; protected set; }
        public sbyte PositionShiftY { get; protected set; }

        /// <summary>
        /// By default, the Z-Shift is set to the terrain height at the position trackable's 
        /// coordinates. In case of resource stacks or animated movables this isn't sufficient.
        /// So if this field has a value, it will be used as Z-Shift instead.
        /// </summary>
        public Double? ZShiftOverride { get; set; }
        public bool IsCentered { get; set; }

        public Double Opacity { get { return m_Alpha / 255.0; } set { m_Alpha = (byte)(255 * Math.Max(0, Math.Min(1.0, value))); } }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="inParent">The parent renderer.</param>
        internal RenderableVisual(TerrainRenderer inParent, CyclePoint inInitialPosition)
        {
            if (inParent == null)
                throw new ArgumentNullException();

            Renderer = inParent;
            Scale = 1;
            IsVisible = true;
            BuildingProgress = 1.0;
            Position = inInitialPosition;
        }

        public virtual void SetAnimationTime(Int64 inTime) { }

        /// <summary>
        /// Renders the object with default parameters.
        /// </summary>
        internal abstract void Render(RenderPass inPass);

        /// <summary>
        /// Is intended for derived classes and provides a way to fine tune the final
        /// size of the rendered object according to additional frame size and offsets.
        /// </summary>
        /// <param name="inOffsetX">Virtual pixel offset in X direction.</param>
        /// <param name="inOffsetY">Virtual pixel offset in Y direction.</param>
        /// <param name="inWidth">Virtual pixel width; overrides the instance width for this call.</param>
        /// <param name="inHeight">Virtual pixel height; overrides the instance height for this call.</param>
        internal void Render(RenderPass inPass, int inFrameIndex, Double inOffsetX, Double inOffsetY, Double inWidth, Double inHeight)
        {
            if (!IsVisible)
                return;

            double ortho = 1.41421356;
            double x = Position.X + PositionShiftX + (IsCentered ? (inOffsetX - inWidth / 2) : inOffsetX) * Scale;
            double y = Position.Y + PositionShiftY + (IsCentered ? (inOffsetY - inHeight / 2) : inOffsetY) * Scale * ortho;

            if ((Renderer.SpriteEngine == null) || (inFrameIndex < 0))
            {
                Texture.Bind();

                Renderer.DrawAtlas(
                    this,
                    (float)x, (float)y, GetZShift(),
                    (float)(inWidth * Scale), (float)(inHeight * Scale * ortho),
                    255, 255, 255, m_Alpha,
                    0, 0, 1, 1);
            }
            else
            {
                Renderer.SpriteEngine.RadixRenderSchedule(inFrameIndex, (texCoords) =>
                {
                    // Correct texture and QUAD mode is already activated by sprite engine.
                    // ATTENTION: Keep in mind that this method is executed asynchronously!
                    Renderer.DrawAtlasStripe(
                        this,
                        (float)x, (float)y + GetZShift(), -(float)((Position.Y + PositionShiftY) / 2000.0),
                        (float)(inWidth * Scale), (float)(inHeight * Scale * ortho),
                        255, 255, 255, m_Alpha,
                        (float)texCoords.Left, (float)texCoords.Top, (float)texCoords.Right, (float)texCoords.Bottom);
                });
            }
        }

        public virtual float GetZShift()
        {
            if ((ZShiftOverride != null) && (ZShiftOverride.HasValue))
                return (float)ZShiftOverride.Value;

            return Renderer.Terrain.GetZShiftAt(Position.XGrid, Position.YGrid);
        }
    }
}
